/*
 * Copyright 2022-2023 Bytedance Ltd. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.bytedance.bitsail.common.util;

import javax.annotation.Nullable;

/**
 * @desc:
 */
public class Preconditions {
// ------------------------------------------------------------------------
  //  Null checks
  // ------------------------------------------------------------------------

  /**
   * Private constructor to prevent instantiation
   */
  private Preconditions() {
  }

  /**
   * Ensures that the given object reference is not null.
   * Upon violation, a {@code NullPointerException} with no message is thrown.
   *
   * @param reference The object reference
   * @return The object reference itself (generically typed).
   * @throws NullPointerException Thrown, if the passed reference was null.
   */
  public static <T> T checkNotNull(T reference) {
    if (reference == null) {
      throw new NullPointerException();
    }
    return reference;
  }

  /**
   * Ensures that the given object reference is not null.
   * Upon violation, a {@code NullPointerException} with the given message is thrown.
   *
   * @param reference    The object reference
   * @param errorMessage The message for the {@code NullPointerException} that is thrown if the check fails.
   * @return The object reference itself (generically typed).
   * @throws NullPointerException Thrown, if the passed reference was null.
   */
  public static <T> T checkNotNull(T reference, @Nullable String errorMessage) {
    if (reference == null) {
      throw new NullPointerException(String.valueOf(errorMessage));
    }
    return reference;
  }

  // ------------------------------------------------------------------------
  //  Boolean Condition Checking (Argument)
  // ------------------------------------------------------------------------

  /**
   * Ensures that the given object reference is not null.
   * Upon violation, a {@code NullPointerException} with the given message is thrown.
   *
   * <p>The error message is constructed from a template and an arguments array, after
   * a similar fashion as {@link String#format(String, Object...)}, but supporting only
   * {@code %s} as a placeholder.
   *
   * @param reference            The object reference
   * @param errorMessageTemplate The message template for the {@code NullPointerException}
   *                             that is thrown if the check fails. The template substitutes its
   *                             {@code %s} placeholders with the error message arguments.
   * @param errorMessageArgs     The arguments for the error message, to be inserted into the
   *                             message template for the {@code %s} placeholders.
   * @return The object reference itself (generically typed).
   * @throws NullPointerException Thrown, if the passed reference was null.
   */
  public static <T> T checkNotNull(T reference,
                                   @Nullable String errorMessageTemplate,
                                   @Nullable Object... errorMessageArgs) {

    if (reference == null) {
      throw new NullPointerException(format(errorMessageTemplate, errorMessageArgs));
    }
    return reference;
  }

  /**
   * Checks the given boolean condition, and throws an {@code IllegalArgumentException} if
   * the condition is not met (evaluates to {@code false}).
   *
   * @param condition The condition to check
   * @throws IllegalArgumentException Thrown, if the condition is violated.
   */
  public static void checkArgument(boolean condition) {
    if (!condition) {
      throw new IllegalArgumentException();
    }
  }

  /**
   * Checks the given boolean condition, and throws an {@code IllegalArgumentException} if
   * the condition is not met (evaluates to {@code false}). The exception will have the
   * given error message.
   *
   * @param condition    The condition to check
   * @param errorMessage The message for the {@code IllegalArgumentException} that is thrown if the check fails.
   * @throws IllegalArgumentException Thrown, if the condition is violated.
   */
  public static void checkArgument(boolean condition, @Nullable Object errorMessage) {
    if (!condition) {
      throw new IllegalArgumentException(String.valueOf(errorMessage));
    }
  }

  // ------------------------------------------------------------------------
  //  Boolean Condition Checking (State)
  // ------------------------------------------------------------------------

  /**
   * Checks the given boolean condition, and throws an {@code IllegalArgumentException} if
   * the condition is not met (evaluates to {@code false}).
   *
   * @param condition            The condition to check
   * @param errorMessageTemplate The message template for the {@code IllegalArgumentException}
   *                             that is thrown if the check fails. The template substitutes its
   *                             {@code %s} placeholders with the error message arguments.
   * @param errorMessageArgs     The arguments for the error message, to be inserted into the
   *                             message template for the {@code %s} placeholders.
   * @throws IllegalArgumentException Thrown, if the condition is violated.
   */
  public static void checkArgument(boolean condition,
                                   @Nullable String errorMessageTemplate,
                                   @Nullable Object... errorMessageArgs) {

    if (!condition) {
      throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageArgs));
    }
  }

  /**
   * Checks the given boolean condition, and throws an {@code IllegalStateException} if
   * the condition is not met (evaluates to {@code false}).
   *
   * @param condition The condition to check
   * @throws IllegalStateException Thrown, if the condition is violated.
   */
  public static void checkState(boolean condition) {
    if (!condition) {
      throw new IllegalStateException();
    }
  }

  /**
   * Checks the given boolean condition, and throws an {@code IllegalStateException} if
   * the condition is not met (evaluates to {@code false}). The exception will have the
   * given error message.
   *
   * @param condition    The condition to check
   * @param errorMessage The message for the {@code IllegalStateException} that is thrown if the check fails.
   * @throws IllegalStateException Thrown, if the condition is violated.
   */
  public static void checkState(boolean condition, @Nullable Object errorMessage) {
    if (!condition) {
      throw new IllegalStateException(String.valueOf(errorMessage));
    }
  }

  /**
   * Checks the given boolean condition, and throws an {@code IllegalStateException} if
   * the condition is not met (evaluates to {@code false}).
   *
   * @param condition            The condition to check
   * @param errorMessageTemplate The message template for the {@code IllegalStateException}
   *                             that is thrown if the check fails. The template substitutes its
   *                             {@code %s} placeholders with the error message arguments.
   * @param errorMessageArgs     The arguments for the error message, to be inserted into the
   *                             message template for the {@code %s} placeholders.
   * @throws IllegalStateException Thrown, if the condition is violated.
   */
  public static void checkState(boolean condition,
                                @Nullable String errorMessageTemplate,
                                @Nullable Object... errorMessageArgs) {

    if (!condition) {
      throw new IllegalStateException(format(errorMessageTemplate, errorMessageArgs));
    }
  }

  /**
   * Ensures that the given index is valid for an array, list or string of the given size.
   *
   * @param index index to check
   * @param size  size of the array, list or string
   * @throws IllegalArgumentException  Thrown, if size is negative.
   * @throws IndexOutOfBoundsException Thrown, if the index negative or greater than or equal to size
   */
  public static void checkElementIndex(int index, int size) {
    checkArgument(size >= 0, "Size was negative.");
    if (index < 0 || index >= size) {
      throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
    }
  }

  // ------------------------------------------------------------------------
  //  Utilities
  // ------------------------------------------------------------------------

  /**
   * Ensures that the given index is valid for an array, list or string of the given size.
   *
   * @param index        index to check
   * @param size         size of the array, list or string
   * @param errorMessage The message for the {@code IndexOutOfBoundsException} that is thrown if the check fails.
   * @throws IllegalArgumentException  Thrown, if size is negative.
   * @throws IndexOutOfBoundsException Thrown, if the index negative or greater than or equal to size
   */
  public static void checkElementIndex(int index, int size, @Nullable String errorMessage) {
    checkArgument(size >= 0, "Size was negative.");
    if (index < 0 || index >= size) {
      throw new IndexOutOfBoundsException(String.valueOf(errorMessage) + " Index: " + index + ", Size: " + size);
    }
  }

  // ------------------------------------------------------------------------

  /**
   * A simplified formatting method. Similar to {@link String#format(String, Object...)}, but
   * with lower overhead (only String parameters, no locale, no format validation).
   *
   * <p>This method is taken quasi verbatim from the Guava Preconditions class.
   */
  @SuppressWarnings("checkstyle:MagicNumber")
  private static String format(@Nullable String template, @Nullable Object... args) {
    final int numArgs = args == null ? 0 : args.length;
    template = String.valueOf(template); // null -> "null"

    // start substituting the arguments into the '%s' placeholders
    StringBuilder builder = new StringBuilder(template.length() + 16 * numArgs);
    int templateStart = 0;
    int i = 0;
    while (i < numArgs) {
      int placeholderStart = template.indexOf("%s", templateStart);
      if (placeholderStart == -1) {
        break;
      }
      builder.append(template.substring(templateStart, placeholderStart));
      builder.append(args[i++]);
      templateStart = placeholderStart + 2;
    }
    builder.append(template.substring(templateStart));

    // if we run out of placeholders, append the extra args in square braces
    if (i < numArgs) {
      builder.append(" [");
      builder.append(args[i++]);
      while (i < numArgs) {
        builder.append(", ");
        builder.append(args[i++]);
      }
      builder.append(']');
    }

    return builder.toString();
  }
}
